VII.IG實戰
今天會分成兩個主題,一個是claim要怎麼寫,另外一個是如果採用stateful的搜尋方式,應該用什麼方法將物件的內容變形,
我們先預設昨天後面的所有Resource除了claim外都已經完成,會特別拉claim出來講的原因有幾個:
首先,claim是一個包裝Resource,將所有TWPAS所用到的大多數應用Resource進行整合,
以欄位來說看起來還滿正常的,有問題的地方出在引用的部分;
從claim profile中有幾個需要注意的問題:
claim當中有supportingInfo、procedure、diagnosis與item這四個欄位,
這四個欄位的共通點就是,他們都有流水編號:
這時候問題就大了,固然可以讓使用者填答的時候自己把流水號、引用的Resource名稱與種類都貼上,
嗯,看起來很蠢,Bundle下面就有的Resource還要手動填一次,最糟糕的是還要寫流水號。
但對於簡化輸入來說是最值得下手的地方,在這裡,簡化的目標是希望直接可以把這整個欄位都省掉,都不要填了
就針對supportingInfo這個欄位來看好了,
其實以最小設計原則來看,supportingInfo只需要填身高、體重與哺乳懷孕狀態等基本資訊,
筆者面對這個欄位的時候曾經有考慮過只寫這三個就好了,不只好設計也減少很多麻煩,
但是持著來都來了的想法,所以開始來思考要怎麼樣可以把supportingInfo的其他內容也一併帶進來,
觀察supportingInfo所需要的欄位,
#FHIR
{
"sequence": 10,
"category": {
"coding": [
{
"system": "https://nhicore.nhi.gov.tw/pas/CodeSystem/nhi-supporting-info-type",
"code": "medicalRecord"
}
]
},
"valueReference": {
"reference": "DocumentReference/doc-medicalRecord-min"
}
},
總共需要流水號、種類的code與引用三個,
思路大概是這樣的:
#FUME
$claim_supportingInfo_count :=
[
$count(diagnosticReportImages),
$count(observationCancerStages),
$count(diagnosticReports),
$count(observationDiagnostics),
$count(documentReferences),
$count(observationLaboratoryResults),
$count(observationPatientAssessments),
$count(medicationRequestTreats),
$count(procedures),
$count(observationTreatmentAssessments)
];
$claim_supportingInfo_total := $sum($claim_supportingInfo_count);
claim_supportingInfo_count是一個帶有各Resource數量的array,
然後令一個claim_supportingInfo_total把所有的count加總起來,兩個都是備用
觀察CodeSystem/nhi-supporting-info-type的內容
https://nhicore.nhi.gov.tw/pas/CodeSystem/nhi-supporting-info-type
可以看到除了weight, height與pregnancyBreastfeedingStatus之外,還有11項可用
這11項裡面,medicalRecord與carePlanDocument都是DocumentReference,並且沒有Profile的差異,
為了計算方便我先統一使用為medicalRecord。
#FUME
$values := [
"imagingReport",
"cancerStage",
"examinationReport",
"geneInfo",
"medicalRecord",
"tests",
"patientAssessment",
"medicationRequest",
"radiotherapy",
"treatmentAssessment"
];
再來處理用來標示reference的前綴ResourceType:
#FUME
$values := [
"DiagnosticReport",
"Observation",
"DiagnosticReport",
"Observation",
"DocumentReference",
"Observation",
"Observation",
"MedicationRequest",
"Procedure",
"Observation"
];
之所以設計成這樣的原因是為了跟最上方在計算各個Profile Resource的index標齊,後面會用到。
#FUME
$claim_supportingInfo_id := (
[
diagnosticReportImages.diagnosticReportImage_id,
observationCancerStages.observationCancerStage_id,
diagnosticReports.diagnosticReport_id,
observationDiagnostics.observationDiagnostic_id,
documentReferences.documentReference_id,
observationLaboratoryResults.observationLaboratoryResult_id,
observationPatientAssessments.observationPatientAssessment_id,
medicationRequestTreats.medicationRequestTreat_id,
procedures.procedure_id,
observationTreatmentAssessments.observationTreatmentAssessment_id
]
);
#FUME
$claim_supportingInfo_category := (
$values := [
"imagingReport",
"cancerStage",
"examinationReport",
"geneInfo",
"medicalRecord",
"tests",
"patientAssessment",
"medicationRequest",
"radiotherapy",
"treatmentAssessment"
];
$repeat := function($val, $n) {
[1..$n] ? $map([1..$n], function($i) { $val }) : []
};
$zipped := $map([0..($count($values)-1)], function($i) {
$repeat($values[$i], $claim_supportingInfo_count[$i])
});
$reduce($zipped, function($acc, $item) {
$append($acc, $item)
}, [])
);
假設$claim_supportingInfo_count的內容為[1,2,3,1,1,1,1, ...]
這裡的幾個function的意義是將各個resource的個數同樣映射數量給原來的category.coding.code
最後把這些各自得到數量的array串接起來
這個物件的內容會變成:
#FHIR
[
"imagingReport",
"cancerStage",
"cancerStage",
"examinationReport",
"examinationReport",
"examinationReport",
"geneInfo",
"medicalRecord",
"tests",
"patientAssessment",
...
];
ResourceType的前綴也是一樣的處理方式。
#FUME
$claim_supportingInfo_total := $sum($claim_supportingInfo_count);
$claim_supportingInfo_seq := ([4..$claim_supportingInfo_total+4]);
$claim_supportingInfo_from := 0;
$claim_supportingInfo := $map([$claim_supportingInfo_from..($claim_supportingInfo_total - 1)], function($idx) {
{
"sequence" : $claim_supportingInfo_seq[$idx],
"category" : $claim_supportingInfo_category[$idx],
"reference" : $claim_supportingInfo_reference[$idx] & '/' & $claim_supportingInfo_id[$idx]
}
});
簡單來說就是定義迴圈的start與end,要求$claim_supportingInfo把這些剛剛整理好的資訊拼在一起,
至於為甚麼seq的範圍是4..totoal+4,因為supportingInfo的sequence的1..3已經被體重身高與哺乳懷孕狀態使用了,所以這個陣列從4開始
按照這個方法把剩下的diagnosis, procedure與item都用類似的手段處理後,可以填上最後一塊拼圖了。
Claim : 1..1
#FUME
$claim_applyReason := (
[
"C50P1",
"C50P2",
"C50R1",
"C16P1",
...
]
);
$claim := $.
(
InstanceOf: Claim
* id = claim_id
* meta
* profile = "https://nhicore.nhi.gov.tw/pas/StructureDefinition/Claim-twpas"
* text
* status = "generated"
* div = ($exists(claim_HTML) ? claim_HTML : $wrap(claim_id))
* extension
* url = "https://nhicore.nhi.gov.tw/pas/StructureDefinition/extension-claim-encounter"
* valueReference
* reference = ($exists(encounter_id) ? 'Encounter/' & encounter_id)
* identifier
* value = claim_subType_code != "1" ? claim_identifier_value
* status = "active"
* type
* coding
* system = ($exists(claim_type_code) ? "http://terminology.hl7.org/CodeSystem/claim-type")
* code = claim_type_code
* subType
* coding
* system = ($exists(claim_subType_code) ? "https://nhicore.nhi.gov.tw/pas/CodeSystem/nhi-apply-type")
* code = claim_subType_code
* display = (claim_subType_code = "1" ? "送核" : claim_subType_code = "2" ? "送核補件" : claim_subType_code = "3" ? "申復" : claim_subType_code = "4" ? "爭議審議" : claim_subType_code = "5" ? "申復補件")
* use = "preauthorization"
* patient
* reference = ($exists(patient_id) ? 'Patient/' & patient_id)
* created = $exists(claim_created) ? claim_created : $now('[Y0001]-[M01]-[D01]')
* enterer
* reference = ($exists(claim_enterer) ? 'Practitioner/' & claim_enterer)
* provider
* reference = ($exists(organization_id) ? 'Organization/' & organization_id)
* priority
* coding
* system = ($exists(claim_priority_code) ? "https://nhicore.nhi.gov.tw/pas/CodeSystem/nhi-tmhb-type")
* code = claim_priority_code
* display = (claim_priority_code = "1" ? "一般事前審查申請" : claim_priority_code = "3" ? "自主審查報備" : claim_priority_code = "4" ? "緊急報備")
* supportingInfo
* sequence = 1
* category
* coding
* system = "https://nhicore.nhi.gov.tw/pas/CodeSystem/nhi-supporting-info-type"
* code = "weight"
* valueQuantity
* value = claim_supportingInfo_weight_value
* system = $ucum
* code = "kg"
* supportingInfo
* sequence = 2
* category
* coding
* system = "https://nhicore.nhi.gov.tw/pas/CodeSystem/nhi-supporting-info-type"
* code = "height"
* valueQuantity
* value = claim_supportingInfo_height_value
* system = $ucum
* code = "cm"
* supportingInfo
* sequence = 3
* category
* coding
* system = "https://nhicore.nhi.gov.tw/pas/CodeSystem/nhi-supporting-info-type"
* code = "pregnancyBreastfeedingStatus"
* valueBoolean = (patient_gender = "female" ? claim_supportingInfo_pregnancyBreastfeedingStatus_value : "false")
* ($claim_supportingInfo).supportingInfo // 引用資源,根據包裝好的物件進行配對
* sequence = sequence
* category
* coding
* system = "https://nhicore.nhi.gov.tw/pas/CodeSystem/nhi-supporting-info-type"
* code = category
* valueReference
* reference = reference
* ($claim_diagnosis).diagnosis
* extension
* url = (sequence = 1 ? "http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-diagnosisRecordedDate")
* valueDate = (sequence = 1 ? body.claim_diagnosis_valueDate)
* sequence = sequence
* diagnosisCodeableConcept
* coding
* system = body.claim_diagnosis_system
* code = body.claim_diagnosis_code
* type
* text = body.claim_diagnosis_text
* ($claim_procedure).procedure
* sequence = sequence
* date = body.claim_procedure_date
* procedureCodeableConcept
* coding
* system = body.claim_procedure_system
* code = body.claim_procedure_code
* insurance
* sequence = "1"
* focal = $boolean("true")
* coverage
* reference = ($exists(coverage_id) ? 'Coverage/' & coverage_id)
* ($claim_item).item
* extension
* url = ($exists(body.claim_item_reference) ? "https://nhicore.nhi.gov.tw/pas/StructureDefinition/extension-requestedService")
* valueReference
* reference = ($exists(body.claim_item_reference) ? 'MedicationRequest/' & body.claim_item_reference)
* sequence = sequence
* productOrService
* coding
* system = ($exists(body.claim_item_productOrService_code) ? "https://nhicore.nhi.gov.tw/pas/CodeSystem/nhi-order-type")
* code = body.claim_item_productOrService_code
* modifier
* coding
* system = "https://nhicore.nhi.gov.tw/pas/CodeSystem/nhi-continuation-status"
* code = body.claim_item_modifier_continuation_code
* modifier
* coding
* system = $exists(body.claim_item_modifier_lot_code) ? "https://nhicore.nhi.gov.tw/pas/CodeSystem/nhi-line-of-therapy"
* code = body.claim_item_modifier_lot_code
* programCode
* coding
* system = (body.claim_item_programCode in $claim_applyReason) ? "https://nhicore.nhi.gov.tw/pas/CodeSystem/nhi-apply-reason"
* code = (body.claim_item_programCode in $claim_applyReason) ? body.claim_item_programCode
* text = $not(body.claim_item_programCode in $claim_applyReason) ? body.claim_item_programCode & " " & body.claim_item_programCode_text : body.claim_item_programCode_text
* quantity
* system = ($exists(body.claim_item_quantity_value) ? $ucum)
* value = body.claim_item_quantity_value
* unit = body.claim_item_quantity_unit
* code = "1"
* bodySite
* coding
* system = ($exists(body.claim_item_bodySite_code) ? "https://nhicore.nhi.gov.tw/pas/CodeSystem/nhi-apply-side")
* code = body.claim_item_bodySite_code
);
#INPUT
{
"claim_id" : "cla-1",
"claim_HTML" : "<div xmlns=\"http://www.w3.org/1999/xhtml\">claim_1</div>",
"claim_identifier_value" : "123456789",
"claim_type_code" : "institutional",
"claim_subType_code" : "1",
"claim_created" : "2024-05-30",
"claim_enterer" : "pra-min",
"claim_priority_code" : "1",
"claim_supportingInfo_weight_value" : "55.6",
"claim_supportingInfo_height_value" : "177",
"claim_supportingInfo_pregnancyBreastfeedingStatus_value" : "true",
"claim_diagnosis" : [
{
"claim_diagnosis_valueDate" : "2024-01-01",
"claim_diagnosis_system" : "https://twcore.mohw.gov.tw/ig/twcore/CodeSystem/icd-10-cm-2023-tw",
"claim_diagnosis_code" : "I50.812",
"claim_diagnosis_text" : "cT3N2M1a"
}
],
"claim_procedure" : [
{
"claim_procedure_date" : "2024-01-01",
"claim_procedure_system" : "https://twcore.mohw.gov.tw/ig/twcore/CodeSystem/icd-10-pcs-2023-tw",
"claim_procedure_code" : "D7Y08ZZ"
}
],
"claim_item" : [
{
"claim_item_reference" : "medReq-apply",
"claim_item_productOrService_code" : "1",
"claim_item_modifier_continuation_code" : "1",
"claim_item_modifier_lot_code" : "1",
"claim_item_programCode" : "P091",
"claim_item_programCode_text" : "AKL陽性",
"claim_item_quantity_value" : "52",
"claim_item_quantity_unit" : "tbl",
"claim_item_bodySite_code" : "R"
}
],
}
做到這一步結束之後,理論上應該能把所有資訊綁在一起轉換出一個符合TWPAS IG的Bundle了,
上面的資訊僅做為參考,讀者還是需要自行將欄位的定義閱讀清楚,並且自定義輸入欄位進行實作。
再來談一下,如果我今天已經有現成的Resource放在我的FHIR Server中,我要怎麼來組Bundle,
我們可以把Bundle恢復成前幾天的狀態
#FUME
(
$serverURL := "https://test.com.tw/fhir/"
$entries := (
);
InstanceOf: Bundle
* id = bundle_id
* meta
* profile = bundle_profile
* type = 'collection'
* timestamp = $now()
* ($entries).entry
* resource = $
* fullUrl = $serverURL & resourceType & ($exists(id) ? '/' & id : '' )
)
事實上我們集中關心$entries,因為$entries是用來串接Bundle的主要物件,
接著對entries改寫成類似於這樣的結構:
#FUME
$entries := $.(
$claim := (
$cla_id := $exists(claim_id) ? claim_id : '-1';
$search("Claim?_id=" & $cla_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Claim-twpas").entry.resource
);
$encounter := (
$enc_id := $exists(encounter_id) ? encounter_id : '-1';
$search("Encounter?_id=" & $enc_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Encounter-twpas").entry.resource
);
$patient := (
$pat_id := $exists(patient_id) ? patient_id : '-1';
$search("Patient?_id=" & $pat_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Patient-twpas").entry.resource
);
$practitioner := (practitioners).(
$pra_id := $exists(practitioner_id) ? practitioner_id : '-1';
$search("Practitioner?_id=" & $pra_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Practitioner-twpas").entry.resource
);
$organization := (
$orgGen_id := $exists(organization_id) ? organization_id : '-1';
$search("Organization?_id=" & $orgGen_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Organization-twpas").entry.resource
);
$organizationGen := (organizationGen).(
$orgGen_id := $exists(organizationGen_id) ? organizationGen_id : '-1';
$search("Organization?_id=" & $orgGen_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Organization-genetic-testing-twpas").entry.resource
);
$diagnosticReportImage := (diagnosticReportImages).(
$diaRepIma_id := $exists(diagnosticReportImage_id) ? diagnosticReportImage_id : '-1';
$search("DiagnosticReport?_id=" & $diaRepIma_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/DiagnosticReport-image-twpas").entry.resource
);
$imagingStudy := (imagingStudies).(
$imaStu_id := $exists(imagingStudy_id) ? imagingStudy_id : '-1';
$search("ImagingStudy?_id=" & $imaStu_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/ImagingStudy-twpas").entry.resource
);
$media := (media).(
$media_id := $exists(media_id) ? media_id : '-1';
$search("Media?_id=" & $media_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Media-twpas").entry.resource
);
$observationCancerStage := (observationCancerStages).(
$obsCancer_id := $exists(observationCancerStage_id) ? observationCancerStage_id : '-1';
$search("Observation?_id=" & $obsCancer_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Observation-cancer-stage-twpas").entry.resource
);
$diagnosticReport := (diagnosticReports).(
$diaRep_id := $exists(diagnosticReport_id) ? diagnosticReport_id : '-1';
$search("DiagnosticReport?_id=" & $diaRep_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/DiagnosticReport-twpas").entry.resource
);
$observationDiagnostic := (observationDiagnostics).(
$obsDiag_id := $exists(observationDiagnostic_id) ? observationDiagnostic_id : '-1';
$search("Observation?_id=" & $obsDiag_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Observation-diagnostic-twpas").entry.resource
);
$specimen := (specimens).(
$spe_id := $exists(specimen_id) ? specimen_id : '-1';
$search("Specimen?_id=" & $spe_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Specimen-twpas").entry.resource
);
$documentReference := (documentReferences).(
$doc_id := $exists(documentReference_id) ? documentReference_id : '-1';
$search("DocumentReference?_id=" & $doc_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/DocumentReference-twpas").entry.resource
);
$observationLaboratoryResult := (observationLaboratoryResults).(
$obsLab_id := $exists(observationLaboratoryResult_id) ? observationLaboratoryResult_id : '-1';
$search("Observation?_id=" & $obsLab_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Observation-laboratory-result-twpas").entry.resource
);
$observationPatientAssessment := (observationPatientAssessments).(
$obsPat_id := $exists(observationPatientAssessment_id) ? observationPatientAssessment_id : '-1';
$search("Observation?_id=" & $obsPat_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Observation-pat-assessment-twpas").entry.resource
);
$medicationRequestTreat := (medicationRequestTreats).(
$medReqTreat_id := $exists(medicationRequestTreat_id) ? medicationRequestTreat_id : '-1';
$search("MedicationRequest?_id=" & $medReqTreat_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/MedicationRequest-treat-twpas").entry.resource
);
$procedure := (procedures).(
$pro_id := $exists(procedure_id) ? procedure_id : '-1';
$search("Procedure?_id=" & $pro_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Procedure-twpas").entry.resource
);
$substance := (substances).(
$sub_id := $exists(substance_id) ? substance_id : '-1';
$search("Substance?_id=" & $sub_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Substance-twpas").entry.resource
);
$observationTreatmentAssessment := (observationTreatmentAssessments).(
$obsTx_id := $exists(observationTreatmentAssessment_id) ? observationTreatmentAssessment_id : '-1';
$search("Observation?_id=" & $obsTx_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Observation-tx-assessment-twpas").entry.resource
);
$medicationRequestApply := (medicationRequestApplies).(
$medReqApply_id := $exists(medicationRequestApply_id) ? medicationRequestApply_id : '-1';
$search("MedicationRequest?_id=" & $medReqApply_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/MedicationRequest-apply-twpas").entry.resource
);
$coverage := (
$cov_id := $exists(coverage_id) ? coverage_id : '-1';
$search("Coverage?_id=" & $cov_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/Coverage-twpas").entry.resource
);
$claimResponse := (
$claRes_id := $exists(claimResponse_id) ? claimResponse_id : '-1';
$search("Bundle?_id=" & $claRes_id & "&_profile=https://nhicore.nhi.gov.tw/pas/StructureDefinition/ClaimResponse-self-assessment-twpas").entry.resource
);
$organizationOrg := (
$search("Organization?_id=" & "org-nhi").entry.resource
);
[$claim, $encounter, $patient, $practitioner, $organization, $organizationGen, $diagnosticReportImage, $imagingStudy, $media, $observationCancerStage, $diagnosticReport, $observationDiagnostic, $specimen, $documentReference, $observationLaboratoryResult, $observationPatientAssessment, $medicationRequestTreat, $procedure, $substance, $observationTreatmentAssessment, $medicationRequestApply, $coverage, $claimResponse, $organizationOrg];
);
其實也不難理解,使用者輸入指定的resource id,FUME就藉由這個id去向FHIR Server要這個Resource,並且放入進物件中,
最後再透過$entries把它串回Bundle,
這樣子的好處是可以將FHIR Resource與FHIR Server協作,不用一次性的把所有資料上傳進FUME,
而是FUME根據符合條件的資料去FHIR Server尋找並套用轉換,
這個流程是比較適用於醫療系統FHIR化的,但唯一沒改變的就是,原始得到的資料還是要透過FUME先轉換成Resource存在FHIR Server中,
就看讀者個人怎麼選擇。
此外,之前放在一邊的TWCI IG,主要的內容都是放在QuestionnaireResponse這個欄位,
因此當使用者填答完畢上傳至FHIR Server後,FUME只要用$search就能把它撈下來拼進Bundle中,最大化組合的靈活度。
其實到這邊FUME的部分就差不多結束了,明天來整理一下實際上筆者在思考怎麼偷欄位的解決方法,
很多方法只是一時適用,但邏輯基礎並不夠完備,
因此實作者必須要掌握好代碼與欄位輸入的關係,在不會出現問題的範圍內找出簡化的辦法。